using System.Collections.Generic;
using SqlQueryBuilder.Clauses;
using SqlQueryBuilder.Criterias;
using SqlQueryBuilder.Util;

namespace SqlQueryBuilder.Queries
{
    /// <summary>
    /// SelectQuery
    /// </summary>
    public class SelectQuery : ISelectQuery
    {
        #region Properties

        private readonly From fromClause;
        private readonly List<Join> joins;
        private readonly Select selectClause;
        private readonly Where whereClause;
        private readonly OrderBy orderByClause;
        private readonly GroupBy groupByClause;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectQuery"/> class.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        public SelectQuery(string tableName) : this(tableName, "") {}

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectQuery"/> class.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="alias">The alias.</param>
        public SelectQuery(string tableName, string alias)
        {
            fromClause = new From(tableName, alias);
            selectClause = new Select();
            whereClause = new Where();
            joins = new List<Join>();
            orderByClause = new OrderBy();
            groupByClause = new GroupBy();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectQuery"/> class.
        /// </summary>
        public SelectQuery()
        {
            fromClause = null;
            selectClause = new Select();
            whereClause = new Where();
            joins = new List<Join>();
            orderByClause = new OrderBy();
            groupByClause = new GroupBy();
        }

        #endregion

        #region IQuery Members

        /// <summary>
        /// Selects the specified fields.
        /// </summary>
        /// <param name="fields">The camps.</param>
        /// <returns></returns>
        public ISelectQuery Select(params SelectClause[] fields)
        {
            foreach (var field in fields)
            {
                selectClause.selectClauses.Add(field);
            }
            return this;
        }

        /// <summary>
        /// Selects all.
        /// </summary>
        /// <returns></returns>
        public ISelectQuery SelectAll()
        {
            selectClause.selectAll = true;
            return this;
        }

        /// <summary>
        /// Tops the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public ISelectQuery Top(int value)
        {
            selectClause.top = new Top(value);
            return this;
        }

        /// <summary>
        /// Distincts this instance.
        /// </summary>
        /// <returns></returns>
        public ISelectQuery Distinct()
        {
            selectClause.isDistinct = true;
            return this;
        }

        /// <summary>
        /// Wheres the specified clause.
        /// </summary>
        /// <param name="clause">The clause.</param>
        /// <returns></returns>
        public IWhereQuery Where(IConditionalClause clause)
        {
            whereClause.whereclause = clause;
            return this;
        }

        #region Join

        /// <summary>
        /// Joins the specified join type.
        /// </summary>
        /// <param name="joinType">Type of the join.</param>
        /// <param name="joinTable">The join table.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery Join(JoinType joinType, string joinTable, IConditionalClause joinClause)
        {
            joins.Add(new Join(joinType, joinTable, joinClause));
            return this;
        }

        /// <summary>
        /// Joins the specified join type.
        /// </summary>
        /// <param name="joinType">Type of the join.</param>
        /// <param name="joinTable">The join table.</param>
        /// <param name="alias">The alias.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery Join(JoinType joinType, string joinTable, string alias, IConditionalClause joinClause)
        {
            joins.Add(new Join(joinType, joinTable, alias, joinClause));
            return this;
        }

        /// <summary>
        /// Inners the join.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="alias">The alias.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery InnerJoin(string joinTable, string alias, IConditionalClause joinClause)
        {
            return Join(JoinType.InnerJoin, joinTable, alias, joinClause);
        }

        /// <summary>
        /// Inners the join.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery InnerJoin(string joinTable, IConditionalClause joinClause)
        {
            return Join(JoinType.InnerJoin, joinTable, joinClause);
        }

        /// <summary>
        /// Lefts the join.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="alias">The alias.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery LeftJoin(string joinTable, string alias, IConditionalClause joinClause)
        {
            return Join(JoinType.LeftJoin, joinTable, alias, joinClause);
        }

        /// <summary>
        /// Lefts the join.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery LeftJoin(string joinTable, IConditionalClause joinClause)
        {
            return Join(JoinType.LeftJoin, joinTable, joinClause);
        }

        /// <summary>
        /// Rights the join.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="alias">The alias.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery RightJoin(string joinTable, string alias, IConditionalClause joinClause)
        {
            return Join(JoinType.RightJoin, joinTable, alias, joinClause);
        }

        /// <summary>
        /// Rights the join.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery RightJoin(string joinTable, IConditionalClause joinClause)
        {
            return Join(JoinType.RightJoin, joinTable, joinClause);
        }

        /// <summary>
        /// Joins the specified join table.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="alias">The alias.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery Join(string joinTable, string alias, IConditionalClause joinClause)
        {
            return Join(JoinType.Join, joinTable, alias, joinClause);
        }

        /// <summary>
        /// Joins the specified join table.
        /// </summary>
        /// <param name="joinTable">The join table.</param>
        /// <param name="joinClause">The join clause.</param>
        /// <returns></returns>
        public ISelectQuery Join(string joinTable, IConditionalClause joinClause)
        {
            return Join(JoinType.Join, joinTable, joinClause);
        }

        #endregion


        /// <summary>
        /// Orders the by.
        /// </summary>
        /// <param name="orderByFields">The order by fields.</param>
        /// <returns></returns>
        public ISelectQuery OrderBy(params string[] orderByFields)
        {
            return OrderBy(true, orderByFields);
        }

        /// <summary>
        /// Orders the by.
        /// </summary>
        /// <param name="ascendant">if set to <c>true</c> [ascendant].</param>
        /// <param name="orderByFields">The order by fields.</param>
        /// <returns></returns>
        public ISelectQuery OrderBy(bool ascendant, params string[] orderByFields)
        {
            var orderByClauses = new OrderByClause(orderByFields) {IsAscendant = ascendant};
            orderByClause.Clauses.Add(orderByClauses);
            return this;
        }

        /// <summary>
        /// Groups the by.
        /// </summary>
        /// <param name="fields">The fields.</param>
        /// <returns></returns>
        public ISelectQuery GroupBy(params GroupByClause[] fields)
        {
            foreach (var field in fields)
            {
                groupByClause.GroupByClauses.Add(field);
            }
            return this;
        }

        /// <summary>
        /// Builds the query.
        /// </summary>
        /// <returns></returns>
        public string BuildQuery()
        {
            return fromClause != null
                       ? selectClause.BuildCriteria() +
                         fromClause.BuildCriteria() +
                         BuildJoins() +
                         whereClause.BuildCriteria() +
                         groupByClause.BuildCriteria() +
                         orderByClause.BuildCriteria()
                       : selectClause.BuildCriteria();
        }

        /// <summary>
        /// Builds the joins.
        /// </summary>
        /// <returns></returns>
        private string BuildJoins()
        {
            string res = string.Empty;
            foreach (var join in joins)
            {
                res += join.BuildCriteria();
            }
            return res;
        }

        #endregion
    }
}